package com.vmj.natives.java.lang;

import com.vmj.heap.VMClass;
import com.vmj.heap.ClassLoader;
import com.vmj.heap.*;
import com.vmj.heap.VMObject;
import com.vmj.natives.NativeMethod;
import com.vmj.natives.Registry;
import com.vmj.rtda.Frame;
import com.vmj.rtda.OperandStack;
import com.vmj.rtda.Thread;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by yangshoujun on 2017/9/23 下午12:39.
 */
public class Throwable {

    private static final Map<Thread, Integer> stackDepthMap = new HashMap<Thread, Integer>();

    public static final java.lang.String jlClass = "java/lang/Throwable";

    public static void init() {
        Registry.register(jlClass, "fillInStackTrace", "(I)Ljava/lang/Throwable;", new NativeMethod() {
            public void execute(Frame frame) {
                VMObject _this = frame.getLocalVars().getThis();
                frame.getOperandStack().pushRef(_this);
                StackTraceElement[] stes = createStackTraceElements(_this, frame.getThread());
                _this.setExtra(stes);
            }
        });

        Registry.register(jlClass, "getStackTraceDepth", "()I", new NativeMethod() {
            public void execute(Frame frame) {
                Integer count = stackDepthMap.get(frame.getThread());
                if(count == null) {
                    count = 0;
                }
                frame.getOperandStack().pushInt(count);
            }
        });

        Registry.register(jlClass, "getStackTraceElement", "(I)Ljava/lang/StackTraceElement;", new NativeMethod() {
            public void execute(Frame frame) {
                OperandStack stack = frame.getOperandStack();
                VMObject _this = frame.getLocalVars().getThis();
                int index = frame.getLocalVars().getInt(1);
                StackTraceElement[] elements = (StackTraceElement[]) _this.getExtra();

                VMClass clazz = frame.getMethod().getClazz().getClassLoader().loadClass("java/lang/StackTraceElement");
                ClassLoader classLoader = clazz.getClassLoader();
                StackTraceElement ele = elements[index];
                VMObject eleObj = clazz.newObject();

                VMObject methodNameObj = StringPool.getStringObject(classLoader, ele.getMethodName());
                eleObj.setRefVar("methodName", "Ljava/lang/String;", methodNameObj);

                VMObject declaringClassObj = StringPool.getStringObject(classLoader, ele.getClassName());
                eleObj.setRefVar("declaringClass", "Ljava/lang/String;", declaringClassObj);

                VMObject fileNameObj = StringPool.getStringObject(classLoader, ele.getFileName());
                eleObj.setRefVar("fileName", "Ljava/lang/String;", fileNameObj);

                eleObj.setIntVar("lineNumber", ele.getLineNumber());
                stack.pushRef(eleObj);
            }
        });

    }

    private static StackTraceElement[] createStackTraceElements(VMObject tobj, Thread thread) {

        int skip = distanceToObject(tobj.getClazz()) + 1;
        Frame[] frames = thread.getFrames();
        int framesCount = frames.length - skip;
        stackDepthMap.put(thread, framesCount);
        Frame[] noExceptionFrames = new Frame[framesCount];
        java.lang.System.arraycopy(frames, 0, noExceptionFrames, 0, framesCount);
        StackTraceElement[] stes = new StackTraceElement[framesCount];
        for(int i = 0;i < framesCount;i++) {
            stes[i] = createStackTraceElement(noExceptionFrames[framesCount - 1 - i]);
        }
        return stes;
    }

    private static int distanceToObject(VMClass clazz) {
        int distance = 0;
        for(VMClass cls = clazz; cls != null; cls = cls.getSuperClass() ) {
            distance++;
        }
        return distance;
    }

    private static StackTraceElement createStackTraceElement(Frame frame) {
        Method method = frame.getMethod();
        VMClass clazz = method.getClazz();

        StackTraceElement element = new StackTraceElement();
        element.setClassName(clazz.getJavaName());
        element.setFileName(clazz.getSourceFile());
        element.setMethodName(method.getName());
        element.setLineNumber(method.getLineNumber(frame.getNextPc() - 1));
        return element;
    }
}
